package com.gusto.engine.semsim.neighborhood.storedistance.impl;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import com.gusto.engine.semsim.Neighbor;
import com.gusto.engine.semsim.neighborhood.storedistance.SimilarityPersistenceDAO;
import com.gusto.engine.semsim.neighborhood.storedistance.URLToID;

public class SimilarityPersistenceDAOImpl implements SimilarityPersistenceDAO {
	
	private static Logger log = Logger.getLogger(SimilarityPersistenceDAOImpl.class);
	
	private JdbcTemplate jdbcTemplate;
	public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}
	
	private Map<String, String> config;
	public void setConfig(Map<String, String> config) {
		this.config = config;
	}
	
	private URLToID urlToId;
	public void setUrlToId(URLToID urlToId) {
		this.urlToId = urlToId;
	}
	
	/*
	private Double minDistance;
	public void setMinDistance(Double minDistance) {
		this.minDistance = minDistance;
	}
	*/
	
	// TODO integrate
	private Double maxResults;
	public void setMaxResults(Double maxResults) {
		this.maxResults = maxResults;
	}
	
	private class ConfigItem {
		public String table;
		public String elem1;
		public String elem2;
		public String column;
	}
	
	private ConfigItem getConfigItem(String tablecolumn) {
		try {
			ConfigItem configItem = new ConfigItem();
			configItem.table = tablecolumn.substring(0, tablecolumn.indexOf("->")).trim();
			String rest = tablecolumn.substring(tablecolumn.indexOf("->") + 2).trim();
			configItem.elem1 = rest.substring(0, rest.indexOf(",")).trim();
			rest = rest.substring(rest.indexOf(",") + 1).trim();
			configItem.elem2 = rest.substring(0, rest.indexOf(",")).trim();
			configItem.column = rest.substring(rest.indexOf(",") + 1).trim();
			return configItem;
		}
		catch (Exception ex) {
			return null;
		}
	}
	
	public Double getDistance(String context, String resource1, String resource2) {
		if (urlToId != null) {
			resource1 = urlToId.transformURLToId(resource1);
			resource2 = urlToId.transformURLToId(resource2);
		}
		
		if (resource1.compareTo(resource2) < 0) {
			String tmp = resource1;
			resource1 = resource2;
			resource2 = tmp;
		}
		ConfigItem ci = getConfigItem(this.config.get(context));
		
		if (ci != null) {
			String sql = 
				"select " + ci.column + " from " + ci.table + " " +
				"where " + ci.elem1 + " = ? and " + ci.elem2 + " = ? ";
			Object[] params = new Object[] { resource1, resource2 };
			int[] types = new int[] { Types.VARCHAR, Types.VARCHAR };
			
			log.debug(sql);
			log.debug(resource1 + " " + resource2 + "");
			
			try {
				List<Double> result = jdbcTemplate.query(sql, params, types, new DistanceRowMapper(ci));
				if (result.size()>0) {
					return result.get(0);
				}
			}
			catch (Exception ex) {}
		}
		return null;
	}
	
	public void updateDistance(String context, String resource1, String resource2, Double similarity) {
		if (urlToId != null) {
			resource1 = urlToId.transformURLToId(resource1);
			resource2 = urlToId.transformURLToId(resource2);
		}
		
		if (similarity != null) {
			if (true) {
			//if (similarity > this.minDistance) {
				updateDistanceImpl(context, resource1, resource2, similarity);
				log.debug("Distance(" + similarity + ") > Min(" + /*minDistance +*/ ")");
			}
			else {
				log.debug("Not inserted, Distance(" + similarity + ") < Min(" + /*minDistance +*/ ")");
			}
		}
		else {
			log.error("Not inserted, Distance is null");
		}
	}
	
	private void updateDistanceImpl(String context, String resource1, String resource2, Double similarity) {
		if (resource1.compareTo(resource2) < 0) {
			String tmp = resource1;
			resource1 = resource2;
			resource2 = tmp;
		}
		
		ConfigItem ci = getConfigItem(config.get(context));
		if (ci != null) {
			/*+ append */
			String insert = 
				"insert /*+ append */ into " + ci.table + " " + 
				"(" + ci.column + ", " + ci.elem1 + ", " + ci.elem2 + ") " +
				"values (?, ?, ?)";
			
			String update = 
				"update " + ci.table + " " +
				"set " + ci.column + " = ? " +
				"where " + ci.elem1 + " = ? " +
				"and " + ci.elem2 + " = ? ";
			
			Object[] params = new Object[] { similarity, resource1, resource2 };
			//int[] types = new int[] { Types.DOUBLE, Types.INTEGER, Types.INTEGER };
			int[] types = new int[] { Types.DOUBLE, Types.VARCHAR, Types.VARCHAR };
			
			try {
				try {
					log.debug("Insert : " + insert);
					jdbcTemplate.update(insert, params, types);
				}
				catch (Exception e) {
					log.debug("Update : " + update);
					jdbcTemplate.update(update, params, types);
				}
				log.debug("context " + context);
				log.debug("insert/update " + ci.table + " ('" + ci.elem1 + "', '" + ci.elem2 + "', '" + ci.column + "')");
				log.debug("       values " + " ('" + resource1 + "', '" + resource2 + "', '" + similarity + "')");
			}
			catch (Exception ex) {
				ex.printStackTrace();
			}
		}
		
	}
	
	public List<Neighbor> getNeighborhood(String context, String resource, Double minDistance, Integer maxNeighbors) {
		if (urlToId != null) {
			resource = urlToId.transformURLToId(resource);
		}
		
		ConfigItem ci = getConfigItem(this.config.get(context));
		String sql = 
			"select " + ci.elem1 + ", " + ci.elem2 + ", " + ci.column + " from " + ci.table + " " +
			"where " + ci.elem1 + " = ? and " + ci.column + " > ? " + 
			"union " +
			"select " + ci.elem1 + ", " + ci.elem2 + ", " + ci.column + " from " + ci.table + " " +
			"where " + ci.elem2 + " = ? and " + ci.column + " > ? " +
			"order by "+ ci.column + " desc " +
			"limit " + maxNeighbors;
		Object[] params = new Object[] { resource, minDistance, resource, minDistance };
		int[] types = new int[] { Types.VARCHAR, Types.DOUBLE, Types.VARCHAR, Types.DOUBLE };
		
		log.debug(sql);
		log.debug(resource + "");
		
		try {
			List<Neighbor> result = jdbcTemplate.query(sql, params, types, new NeighborRowMapper(ci, resource));
			return result;
		}
		catch (Exception ex) {
			ex.printStackTrace();
		}
		
		return null;
	}
	
	public void cleanDistances(String context) {
		if (config.containsKey(context)) {
			ConfigItem ci = getConfigItem(config.get(context));
			
			String clean = 
				"update " + ci.table + " " +
				"set " + ci.column + " = null ";
			log.debug("clean '" + context + "' " + clean);
			//jdbcTemplate.update(clean);
			// FIXME fix cleaning mechanism
		}
	}
	
	class DistanceRowMapper implements RowMapper {
		
		ConfigItem ci;
		
		public DistanceRowMapper(ConfigItem ci) {
			this.ci = ci;
		}
		
		public Object mapRow(ResultSet rs, int index) throws SQLException {
			Double dist = Double.parseDouble(rs.getObject(ci.column)+"");
		    return dist;
		}
	}
	
	class NeighborRowMapper implements RowMapper {
		
		String resource;
		ConfigItem ci;
		
		public NeighborRowMapper(ConfigItem ci, String resource) {
			this.resource = resource;
			this.ci = ci;
		}
		
		public Object mapRow(ResultSet rs, int index) throws SQLException {
			Neighbor neighbor = new Neighbor();
			String nid = rs.getString(ci.elem1);
			if (nid.equals(resource)) {
				nid = rs.getString(ci.elem2);
			}
			Double dist = Double.parseDouble(rs.getObject(ci.column)+"");
			neighbor.setNeighbor(nid);
			neighbor.setSimilarity(dist);
		    return neighbor;
		}
	}
	
}
